using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Threading;

namespace WbsEditor.Generation
{
    public class WbsGenerator
    {
        public WbsGenerator(WbsTfsClient tfsClient)
        {
            _tfsClient = tfsClient;
        }

        #region Fields and Properties
        private WbsTfsClient _tfsClient;
        public WbsTfsClient TfsClient
        {
            get { return _tfsClient; }
        }
        #endregion

        #region Public Methods
        public WorkBreakdownStructure GenerateWbs(string assignedTo, bool includeOrphanTasks, bool includeClosed, bool includeBugs, bool refreshWbsNodes)
        {
            try
            {
                WorkBreakdownStructure generatedWbs = new WorkBreakdownStructure();
                generatedWbs.Root.Title = assignedTo + ", " + TfsClient.Iteration;
                generatedWbs.Title = assignedTo + ", " + TfsClient.Iteration;

                string wiq = string.Format("SELECT [System.Id], [System.Title], [System.AssignedTo] FROM WorkItems WHERE [System.TeamProject]='{0}' AND ([System.Description] CONTAINS '<WbsXml' OR [System.Title] CONTAINS '(WBS Editor)') AND [System.IterationPath] = '{1}' ORDER BY [System.Title]",
                    TfsClient.TeamProject,
                    TfsClient.Iteration);

                Dictionary<int, bool> handledWorkItemIds = new Dictionary<int,bool>();
                WorkItemCollection wiColl = TfsClient.GetWorkItemsFromQuery(wiq);
                foreach (WorkItem wbsWi in wiColl)
                {
                    try
                    {
                        List<WbsNode> assignedToWbsNodes = new List<WbsNode>();
                        WorkBreakdownStructure wbs = WbsSerializer.DeserializeFromString(wbsWi.Description);
                        int currentId = wbsWi.Id;
                        handledWorkItemIds[currentId] = true;
                        GatherNodesWithAssignedTo(wbs, assignedTo, assignedToWbsNodes);

                        if (assignedToWbsNodes.Count > 0)
                        {
                            WbsNode conceptNode = new WbsNode("");
                            conceptNode.Title = wbs.TID + " - " + wbs.Root.Title;
                            conceptNode.AssignedTo = wbs.Root.AssignedTo;
                            conceptNode.WorkItemId = wbs.Root.WorkItemId;
                            conceptNode.IsLinkToWbs = true;

                            foreach (WbsNode node in assignedToWbsNodes)
                            {
                                if (refreshWbsNodes)
                                {
                                    TfsClient.RefreshWbsNode(wbs, node);
                                }
                                if (!node.Iteration.Equals(TfsClient.Iteration, StringComparison.InvariantCultureIgnoreCase))
                                    continue;

                                if ((node.State != "Closed") || includeClosed)
                                {
                                    node.Title = node.PspCode + ": " + node.Title;
                                    conceptNode.AddChild(node);
                                    if (node.IsPublished)
                                        handledWorkItemIds[node.WorkItemId] = true;
                                }
                            }

                            if (conceptNode.ChildNodes.Count > 0)
                            {
                                generatedWbs.Root.AddChild(conceptNode);
                            }
                        }
                    }
                    catch (Exception)
                    {
                        // Ignore
                    }
                }

                if (includeOrphanTasks || includeBugs)
                {
                    string filter = string.Format("AND [System.AssignedTo] = '{0}' ", assignedTo);
                    if (includeOrphanTasks && includeBugs)
                    {
                        filter += "AND [System.WorkItemType] IN ('Task', 'Bug')";
                    }
                    else if (includeOrphanTasks && !includeBugs)
                    {
                        filter += "AND [System.WorkItemType] = 'Task'";
                    }
                    else if (!includeOrphanTasks && includeBugs)
                    {
                        filter += "AND [System.WorkItemType] = 'Bug'";
                    }
                    if (!includeClosed)
                    {
                        filter += " AND [System.State] <> 'Closed'";
                    }
                    wiq = string.Format("SELECT [System.Id], [System.Title], [System.AssignedTo] FROM WorkItems WHERE [System.TeamProject]='{0}' AND [System.IterationPath] = '{1}' {2} ORDER BY [System.Id]",
                        TfsClient.TeamProject,
                        TfsClient.Iteration,
                        filter);

                    wiColl = TfsClient.GetWorkItemsFromQuery(wiq);
                    List<WbsNode> taskNodes = new List<WbsNode>();
                    List<WbsNode> bugNodes = new List<WbsNode>();

                    foreach (WorkItem wi in wiColl)
                    {
                        int id = wi.Id;
                        if (handledWorkItemIds.ContainsKey(id))
                            continue;
                        WbsNode node = MakeNode(wi);
                        handledWorkItemIds[id] = true;
                        if (wi.Type.Name.Equals("Task"))
                        {
                            taskNodes.Add(node);
                        }
                        else
                        {
                            bugNodes.Add(node);
                        }
                    }

                    if (taskNodes.Count > 0)
                    {
                        WbsNode taskNode = new WbsNode("Task");
                        taskNode.AssignedTo = assignedTo;
                        taskNode.Title = "Tasks without WBS assignment";
                        generatedWbs.Root.AddChild(taskNode);
                        foreach (WbsNode node in taskNodes)
                        {
                            taskNode.AddChild(node);
                        }
                    }

                    if (bugNodes.Count > 0)
                    {
                        WbsNode bugNode = new WbsNode("Bug");
                        bugNode.AssignedTo = assignedTo;
                        bugNode.Title = "Bugs";
                        generatedWbs.Root.AddChild(bugNode);
                        foreach (WbsNode node in bugNodes)
                        {
                            bugNode.AddChild(node);
                        }
                    }
                }

                return generatedWbs;
            }
            catch (Exception)
            {
                return null;
            }
        }

        public WorkPackageInfo[] GenerateWorkPackageInfo(bool includeOrphanTasks, bool includeBugs, bool refreshWbsNodes)
        {
            WorkPackageInfoThreadParams param = new WorkPackageInfoThreadParams();
            param.IncludeOrphanTasks = includeOrphanTasks;
            param.IncludeBugs = includeBugs;
            param.RefreshWbsNodes = refreshWbsNodes;
            Thread thread = new Thread(GenerateWorkPackageInfoThread);
            thread.Start(param);
            param.ProgressDialog.ShowDialog();

            if (param.Success)
                return param.Infos;

            return new WorkPackageInfo[] { };
        }

        public void GenerateWorkloadData(IEnumerable<WorkPackageInfo> infoList)
        {
            foreach (WorkPackageInfo info in infoList)
            {
                GenerateWorkloadData(info);
            }
        }

        public WorkBreakdownStructure GeneratePersonalizedWbsFromWorkPackageInfos(WorkPackageInfo[] infos, string name)
        {
            WorkBreakdownStructure wbs = new WorkBreakdownStructure();
            wbs.Root.Title = name + ", " + TfsClient.Iteration;
            foreach (WorkPackageInfo info in infos)
            {
                List<WbsNode> nodes = new List<WbsNode>();
                string title;
                int workItemId = -1;
                if (info.Type == WorkPackageInfo.WorkPackageType.WorkBreakdownStructure)
                {
                    title = info.WorkBreakdownStructure.TID + ": " + info.WorkBreakdownStructure.Root.Title;
                    workItemId = info.WorkBreakdownStructure.Root.WorkItemId;
                    GatherNodesWithAssignedTo(info.WorkBreakdownStructure, name, nodes);
                }
                else
                {
                    if (info.Type == WorkPackageInfo.WorkPackageType.OrphanTasks)
                        title = "Tasks without WBS assignment";
                    else
                        title = "Bugs";
                    GatherNodesWithAssignedTo(info.WorkItems, name, nodes);
                }

                if (nodes.Count > 0)
                {
                    WbsNode node = new WbsNode("");
                    node.Title = title;
                    node.WorkItemId = workItemId;
                    wbs.Root.AddChild(node);
                    node.AddChildRange(CloneNodeList(nodes));
                }
            }
            return wbs;
        }
        #endregion

        #region Private Methods
        private WbsNode MakeNode(WorkItem workItem)
        {
            WbsNode node = new WbsNode(workItem.Type.Name);
            node.WorkItemId = workItem.Id;
            node.Title = workItem.Title;
            node.Description = workItem.Description;
            node.State = workItem.State;
            node.Iteration = workItem.IterationPath;
            node.AssignedTo = workItem.Fields["Assigned To"].Value.ToString();
            float remainingWork = 0f;
            float completedWork = 0f;
            if (null != workItem.Fields["Remaining Work"].Value)
            {
                if (float.TryParse(workItem.Fields["Remaining Work"].Value.ToString(), out remainingWork))
                    node.RemainingWorkInDays = remainingWork;
            }
            if (null != workItem.Fields["Completed Work"].Value)
            {
                if (float.TryParse(workItem.Fields["Completed Work"].Value.ToString(), out completedWork))
                    node.CompletedWorkInDays = completedWork;
            }
            int priority;
            if (int.TryParse(workItem.Fields["Priority"].Value.ToString(), out priority))
                node.Priority = priority;
            return node;
        }

        private void GatherNodesWithAssignedTo(WorkBreakdownStructure wbs, string assignedTo, List<WbsNode> assignedToWbsNodes)
        {
            foreach (WbsNode child in wbs.Root.ChildNodes)
            {
                GatherNodesWithAssignedTo(child, assignedTo, assignedToWbsNodes);
            }
        }

        private void GatherNodesWithAssignedTo(WbsNode node, string assignedTo, List<WbsNode> assignedToWbsNodes)
        {
            if (node.IsLeaf)
            {
                if (node.AssignedTo.Equals(assignedTo, StringComparison.CurrentCultureIgnoreCase))
                {
                    assignedToWbsNodes.Add(node);
                }
            }

            foreach (WbsNode child in node.ChildNodes)
            {
                GatherNodesWithAssignedTo(child, assignedTo, assignedToWbsNodes);
            }
        }

        private void GatherNodesWithAssignedTo(IEnumerable<WbsNode> nodes, string assignedTo, List<WbsNode> assignedToWbsNodes)
        {
            foreach (WbsNode node in nodes)
            {
                if (node.AssignedTo.Equals(assignedTo, StringComparison.CurrentCultureIgnoreCase))
                {
                    assignedToWbsNodes.Add(node);
                }
            }
        }

        private void GenerateWorkloadData(WorkPackageInfo wpi)
        {
            if (wpi.Type == WorkPackageInfo.WorkPackageType.WorkBreakdownStructure)
            {
                wpi.WorkloadInfo = WbsExporter.ExtractWorkload(wpi.WorkBreakdownStructure.Root, TfsClient.Iteration);
            }
            else
            {
                WorkBreakdownStructure wbsTemp = new WorkBreakdownStructure();
                foreach (WbsNode node in wpi.WorkItems)
                {
                    wbsTemp.Root.AddChild(node);
                }
                PersonWorkload[] workloads = WbsExporter.ExtractWorkload(wbsTemp.Root, TfsClient.Iteration);
                wpi.WorkloadInfo = workloads;
            }
        }

        private void ExtractHandledIds(WorkPackageInfo wpi, Dictionary<int, bool> handledWorkItemIds)
        {
            if (wpi.Type == WorkPackageInfo.WorkPackageType.WorkBreakdownStructure)
            {
                ExtractHandledIdsRecursive(wpi.WorkBreakdownStructure.Root, handledWorkItemIds);
            }
            else
            {
                foreach (WbsNode node in wpi.WorkItems)
                {
                    if (node.IsPublished)
                    {
                        if (!handledWorkItemIds.ContainsKey(node.WorkItemId))
                            handledWorkItemIds[node.WorkItemId] = true;
                    }
                }
            }
        }

        private void ExtractHandledIdsRecursive(WbsNode node, Dictionary<int, bool> handledWorkItemIds)
        {
            if (node.IsPublished)
            {
                if (!handledWorkItemIds.ContainsKey(node.WorkItemId))
                    handledWorkItemIds[node.WorkItemId] = true;
            }
            foreach (WbsNode child in node.ChildNodes)
            {
                ExtractHandledIdsRecursive(child, handledWorkItemIds);
            }
        }

        private List<WbsNode> CloneNodeList(List<WbsNode> nodes)
        {
            List<WbsNode> clone = new List<WbsNode>();
            foreach (WbsNode node in nodes)
            {
                clone.Add(node.Clone());
            }
            return clone;
        }
        #endregion

        #region Work Package Generation
        internal class WorkPackageInfoThreadParams
        {
            internal WorkPackageInfoThreadParams()
            {
                _success = false;
                _progressDlg = new GenerateProgressDialog();
                ThreadFinished += _progressDlg.OnThreadFinished;
            }

            private bool _includeOrphanTasks;
            public bool IncludeOrphanTasks { get { return _includeOrphanTasks; } set { _includeOrphanTasks = value; } }
            private bool _includeBugs;
            public bool IncludeBugs { get { return _includeBugs; } set { _includeBugs = value; } }
            private bool _refreshWbsNodes;
            public bool RefreshWbsNodes { get { return _refreshWbsNodes; } set { _refreshWbsNodes = value; } }
            private WorkPackageInfo[] _infos;
            public WorkPackageInfo[] Infos { get { return _infos; } set { _infos = value; } }
            private bool _success;
            public bool Success { get { return _success; } set { _success = value; } }
            private GenerateProgressDialog _progressDlg;
            public GenerateProgressDialog ProgressDialog { get { return _progressDlg; } }

            public event EventHandler ThreadFinished;

            public void FireThreadFinished(object sender)
            {
                if (null != ThreadFinished)
                {
                    ThreadFinished(sender, new EventArgs());
                }
            }
        }

        private void GenerateWorkPackageInfoThread(object threadParams)
        {
            WorkPackageInfoThreadParams param = (WorkPackageInfoThreadParams)threadParams;
            try
            {
                bool includeOrphanTasks = param.IncludeOrphanTasks;
                bool includeBugs = param.IncludeBugs;
                bool refreshWbsNodes = param.RefreshWbsNodes;

                string wiq = string.Format("SELECT [System.Id], [System.Title], [System.AssignedTo] FROM WorkItems WHERE [System.TeamProject]='{0}' AND ([System.Description] CONTAINS '<WbsXml' OR [System.Title] CONTAINS '(WBS Editor)') AND [System.IterationPath] = '{1}' ORDER BY [System.Title]",
                    TfsClient.TeamProject,
                    TfsClient.Iteration);

                Dictionary<int, bool> handledWorkItemIds = new Dictionary<int, bool>();
                WorkItemCollection wiColl = TfsClient.GetWorkItemsFromQuery(wiq);
                int totalCount = wiColl.Count;
                if (includeOrphanTasks)
                    totalCount++;
                if (includeBugs)
                    totalCount++;
                param.ProgressDialog.Maximum = totalCount;
                param.ProgressDialog.ProgressValue = 0;
                List<WorkPackageInfo> workPackageInfoList = new List<WorkPackageInfo>();
                int seriesId = 0;
                foreach (WorkItem wbsWi in wiColl)
                {
                    param.ProgressDialog.Tick();
                    try
                    {
                        WorkBreakdownStructure wbs = WbsSerializer.DeserializeFromString(wbsWi.Description);
                        param.ProgressDialog.ProgressText = wbs.Root.Title;
                        WorkPackageInfo wpi = new WorkPackageInfo();
                        if (refreshWbsNodes)
                        {
                            TfsClient.RefreshWbs(wbs);
                            wbs.RecalculateTotalWork();
                        }
                        wpi.Type = WorkPackageInfo.WorkPackageType.WorkBreakdownStructure;
                        wpi.WorkBreakdownStructure = wbs;
                        //GenerateWorkloadData(wpi);
                        wpi.SeriesId = seriesId++;

                        workPackageInfoList.Add(wpi);
                    }
                    catch (Exception /*ex*/)
                    {
                    }
                }

                foreach (WorkPackageInfo wpi in workPackageInfoList)
                {
                    ExtractHandledIds(wpi, handledWorkItemIds);
                }

                if (includeOrphanTasks)
                {
                    param.ProgressDialog.Tick();
                    param.ProgressDialog.ProgressText = "Orphan Tasks in Iteration";
                    WorkPackageInfo wpi = new WorkPackageInfo();
                    wpi.Type = WorkPackageInfo.WorkPackageType.OrphanTasks;
                    string workItemType = "Task";
                    GenerateWorkItemPackageData(workItemType, wpi, handledWorkItemIds);
                    if (wpi.WorkItems.Count > 0)
                    {
                        wpi.SeriesId = seriesId++;
                        workPackageInfoList.Add(wpi);
                    }
                }

                if (includeBugs)
                {
                    param.ProgressDialog.Tick();
                    param.ProgressDialog.ProgressText = "Bugs in Iteration";
                    WorkPackageInfo wpi = new WorkPackageInfo();
                    wpi.Type = WorkPackageInfo.WorkPackageType.Bugs;
                    string workItemType = "Bug";
                    GenerateWorkItemPackageData(workItemType, wpi, handledWorkItemIds);
                    if (wpi.WorkItems.Count > 0)
                    {
                        wpi.SeriesId = seriesId++;
                        workPackageInfoList.Add(wpi);
                    }
                }

                param.Infos = workPackageInfoList.ToArray();
                param.Success = true;
            }
            catch (Exception /*ex*/)
            {
                param.Success = false;
            }

            param.FireThreadFinished(this);
        }

        private void GenerateWorkItemPackageData(string workItemType, WorkPackageInfo wpi, Dictionary<int, bool> handledWorkItemIds)
        {
            string filter = string.Format("AND [System.WorkItemType] = '{0}'", workItemType);
            string wiq = string.Format("SELECT [System.Id], [System.Title], [System.AssignedTo] FROM WorkItems WHERE [System.TeamProject]='{0}' AND [System.IterationPath] = '{1}' {2} ORDER BY [System.Id]",
                        TfsClient.TeamProject,
                        TfsClient.Iteration,
                        filter);
            WorkItemCollection wiColl = TfsClient.GetWorkItemsFromQuery(wiq);

            foreach (WorkItem wi in wiColl)
            {
                if (handledWorkItemIds.ContainsKey(wi.Id))
                    continue;

                WbsNode node = MakeNode(wi);
                wpi.WorkItems.Add(node);
                handledWorkItemIds[wi.Id] = true;
            }

            //GenerateWorkloadData(wpi);
        }
        #endregion
    }
}
